package com.atguigu.srb.core.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.srb.base.util.Asserts;
import com.atguigu.srb.common.result.ResponseEnum;
import com.atguigu.srb.core.entity.*;
import com.atguigu.srb.core.enums.LendStatusEnum;
import com.atguigu.srb.core.enums.TransTypeEnum;
import com.atguigu.srb.core.hfb.FormHelper;
import com.atguigu.srb.core.hfb.HfbConst;
import com.atguigu.srb.core.hfb.RequestHelper;
import com.atguigu.srb.core.mapper.LendReturnMapper;
import com.atguigu.srb.core.service.*;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;

/**
 * <p>
 * 还款记录表 服务实现类
 * </p>
 *
 * @author Atguigu
 * @since 2023-06-30
 */
@Service
@Slf4j
public class LendReturnServiceImpl extends ServiceImpl<LendReturnMapper, LendReturn> implements LendReturnService {
    @Resource
    UserInfoService userInfoService;
    @Resource
    UserAccountService userAccountService;
    @Resource
    TransFlowService transFlowService;
    @Resource
    LendItemReturnService lendItemReturnService;
    @Resource
    LendService lendService;
    @Resource
    LendItemService lendItemService;
    @Resource
    BorrowInfoService borrowInfoService;

    @Transactional(rollbackFor = Exception.class)
    // 还款的同步接口
    @Override
    public String repayment(String token, Long lendReturnId) {
        // TODO 根据已有的信息提前查询好所需的数据
        // 校验用户是否绑定hfb账户的同时查询用户相关的信息
        UserInfo borrowerInfo = userInfoService.checkUserInfoByToken(token);
        // 获取还款计划 (此处暂时不获取,后面校验最近的还款计划的地方会查询还款计划对象)
        // LendReturn lendReturn = this.getById(lendReturnId);

        // 1.TODO 校验参数

        // 1.校验当前的回款计划是否是最近的 (根据userId,status) 一个用户最多只能有一个正在进行中的借款,所以这两个条件查询就够了
        // 0-未归还 1-已归还
        //先查询该用户的最近的一期未还的还款计划
        LendReturn lendReturn = this.getOne(Wrappers.lambdaQuery(LendReturn.class).eq(LendReturn::getUserId, borrowerInfo.getId())
                .eq(LendReturn::getStatus, 0)
                .orderByAsc(LendReturn::getCurrentPeriod)
                // last是在sql语句的最后追加语句
                .last("limit 1"));
        //lendReturn 不能为空
        Asserts.AssertTrue(lendReturn !=null && lendReturn.getId().compareTo(lendReturnId) == 0, ResponseEnum.LEND_RETURN_ERROR);
        // 1.校验还款计划是否是当前用户的
        Asserts.AssertTrue(borrowerInfo.getId().compareTo(lendReturn.getUserId()) == 0,ResponseEnum.LEND_RETURN_USER_ERROR);
        // 1.校验还款计划的状态必须是未归还的
        Asserts.AssertTrue(lendReturn.getStatus() == 0,ResponseEnum.LEND_RETURN_ERROR);

        // 1.校验用户的账户余额是否足够用来还款
        //获取用户的账户余额
        BigDecimal amount = userAccountService.getAmount(borrowerInfo.getId());
        BigDecimal total = lendReturn.getTotal();
        Asserts.AssertTrue(amount.compareTo(total) >= 0,ResponseEnum.NOT_SUFFICIENT_FUNDS_ERROR);

        //1.校验是否有逾期
        // 1.TODO 断是否逾期,如果逾期需要计算逾期的费用(这个项目中暂时没有做这个功能)
        long time = lendReturn.getReturnDate().getTime();
        long currentTime = System.currentTimeMillis();
        //只是判断,不抛出异常
        log.info(time <= currentTime ? "没有逾期":"逾期了");

        // 准备汇付宝所需要的参数
        Map<String, Object> params = new HashMap<>();
        params.put("agentId",HfbConst.AGENT_ID);//给商户分配的唯一标识
        //组成 用户名 哪个借款 第多少期还款
        String agentGoodsName = borrowerInfo.getName() + lendReturn.getLendId() + "第" + lendReturn.getCurrentPeriod() + "期还款";
        params.put("agentGoodsName",agentGoodsName);//商户商品名称
        params.put("agentBatchNo",lendReturn.getReturnNo());//批次号 将用户的还款编号放进去,方便回调的时候查询数据
        params.put("fromBindCode",borrowerInfo.getBindCode());//还款人绑定协议号
        params.put("totalAmt",lendReturn.getTotal());//还款总额
        params.put("note",borrowerInfo.getName()+"还款");//备注
        //接下来是构建还款明细数据的时候
        //首先要根据lendReturn查询出来对应的所有的回款的计划列表
        List<LendItemReturn> lendItemReturns = lendItemReturnService.list(Wrappers.lambdaQuery(LendItemReturn.class)
                .eq(LendItemReturn::getLendReturnId, lendReturnId));
        //对列表中的每个回款计划进行遍历,设置对应的属性
        List<Map<String,Object>> data = new ArrayList<>();
        //先根据已有的条件将lend的信息查询出来
        Lend lend = lendService.getById(lendReturn.getLendId());
        lendItemReturns.forEach(lendItemReturn -> {
            Long investUserId = lendItemReturn.getInvestUserId();
            UserInfo investor = userInfoService.getById(investUserId);
            Map<String,Object> paramsMap = new HashMap<>();
            paramsMap.put("agentProjectCode",lend.getLendNo());//还款项目编号
            //查询出来所需的投资记录
            /* LendItem lendItem = lendItemService.getOne(Wrappers.lambdaQuery(LendItem.class).
                    eq(LendItem::getLendId, lendItemReturn.getLendItemId())); */
            LendItem lendItem = lendItemService.getById(lendItemReturn.getLendItemId());
            paramsMap.put("voteBillNo",lendItem.getLendItemNo());//投资单号
            paramsMap.put("toBindCode",investor.getBindCode());//收款人的账户bindCode
            paramsMap.put("transitAmt",lendItemReturn.getTotal());//交易总金额
            paramsMap.put("baseAmt",lendItemReturn.getPrincipal());//本金
            paramsMap.put("benifitAmt",lendItemReturn.getInterest());//利息
            //收取5%的手续费
            BigDecimal fee = lendItemReturn.getInterest().multiply(new BigDecimal(0.05));
            paramsMap.put("feeAmt",fee);//手续费

            //判断下交易流水是否存在(transNo是唯一的,我们可以根据transNo来查询)
            TransFlow transFlow = transFlowService.getByTransNo(lendItemReturn.getId().toString());
            String memo = "投资人id:" +investor.getId() + ",投资记录为:" + lendItem.getLendItemNo();
            if(transFlow != null){
                //进来这里面,说明之前有保存过的交易流水,只需要更新就好了
                transFlow.setTransAmount(lendItemReturn.getTotal().subtract(fee));
                transFlow.setUserId(investor.getId());
                transFlow.setUserName(investor.getName());
                transFlow.setTransType(TransTypeEnum.INVEST_BACK.getTransType());
                transFlow.setTransTypeName(TransTypeEnum.INVEST_BACK.getTransTypeName());
                transFlow.setMemo(memo);
                transFlow.setStatus(0);
                transFlowService.updateById(transFlow);
            }else{
                //进来这里面,说明交易流水不存在,直接新增保存投资人的交易流水
                transFlowService.saveTransFlow(lendItemReturn.getTotal().subtract(fee),
                        investor,lendItemReturn.getId().toString(),TransTypeEnum.INVEST_BACK,0,memo);
            }


            data.add(paramsMap);
        });

        params.put("data", JSON.toJSONString(data));//还款明细数据

        params.put("voteFeeAmt",new BigDecimal(0));//商户手续费 这个地方不会扣借款人还款时候的手续费
        params.put("returnUrl",HfbConst.BORROW_RETURN_RETURN_URL);//
        params.put("notifyUrl",HfbConst.BORROW_RETURN_NOTIFY_URL);//
        params.put("timestamp",System.currentTimeMillis());//时间戳
        String sign = RequestHelper.getSign(params);//获取签名
        params.put("sign",sign);//签名

        //保存借款人的交易流水,状态设置为 0 为完成
        //充值交易状态0 未完成; 1 已完成; 2 失败; 3 resultCode异常; 4 代表验签失败; 5 代表充值金额异常
        TransFlow transFlow = transFlowService.getByTransNo(lendReturn.getReturnNo());
        if(transFlow != null){
            transFlow.setTransAmount(lendReturn.getTotal());
            transFlow.setUserId(borrowerInfo.getId());
            transFlow.setUserName(borrowerInfo.getName());
            transFlow.setTransType(TransTypeEnum.RETURN_DOWN.getTransType());
            transFlow.setTransTypeName(TransTypeEnum.RETURN_DOWN.getTransTypeName());
            transFlow.setMemo("");
            transFlow.setStatus(0);
            transFlowService.updateById(transFlow);
        }else{
            //为空,说明还没有创建过,直接新增即可
            transFlowService.saveTransFlow(lendReturn.getTotal(),borrowerInfo,lendReturn.getReturnNo(), TransTypeEnum.RETURN_DOWN,0,"");
        }
        // 构建form表单
        return FormHelper.buildForm(HfbConst.BORROW_RETURN_URL, params);
    }

    //还款的异步通知接口
    @Transactional(rollbackFor = Exception.class)
    @Override
    public String repaymentNotify(Map<String, Object> params) {
        //resultCode
        // agentBatchNo   lendReturn.getReturnNo()
        String agentBatchNo = params.get("agentBatchNo").toString();

        String paramsInfo = JSON.toJSONString(params);
        //查询借款人的transFlow对象
        TransFlow transFlow = transFlowService.getByTransNo(agentBatchNo);
        //幂等性验证
        //1.1恶意攻击验证
        if(transFlow == null){
            //说明是恶意攻击
            return "fail";
        }
        //1.2验证status
        if(transFlow.getStatus() != 0){
            //进来这里面说明已经执行过一次了
            return transFlow.getStatus() == 1 ? "success":"fail";
        }
        //1.3验签
        if(!RequestHelper.isSignEquals(params)){
            //进来这里面说明签名有问题
            //充值交易状态0 未完成; 1 已完成; 2 失败; 3 resultCode异常; 4 代表验签失败; 5 代表充值金额异常
            log.error("验签失败,{}",paramsInfo);
            transFlow.setStatus(4);
            transFlowService.updateById(transFlow);
            return "fail";
        }

        //1.4验证状态码
        if(!params.containsKey("resultCode")
                || !StringUtils.equals("0001",params.get("resultCode").toString())){
            //进来这里面说明resultCode不存在或者不是0001(也就是不成功)
            log.error("resultCode异常,{}",paramsInfo);
            transFlow.setStatus(3);
            transFlowService.updateById(transFlow);
            return "fail";
        }
        //~~~~~~~~~~~~~~~~~更新借款人相关的表格~~~~~~~~~~~~~~~~~~~~~~~~
        //1.更新还款计划的状态为1  状态（0-未归还 1-已归还）
        //查询还款计划 根据returnNo
        LendReturn lendReturn = this.getOne(Wrappers.lambdaQuery(LendReturn.class)
                .eq(LendReturn::getReturnNo, agentBatchNo));
        lendReturn.setStatus(1);
        lendReturn.setOverdue(false);
        lendReturn.setOverdueTotal(new BigDecimal(0));
        lendReturn.setRealReturnTime(new Date());
        this.updateById(lendReturn);

        //2.更新借款人的账户
        //获取借款人的userId
        Long userId = lendReturn.getUserId();
        userAccountService.updateAmountAndFreezeAmountByUserId(lendReturn.getTotal().negate()
                ,new BigDecimal(0),userId,new Date());

        //3.更新借款人交易流水的状态为已完成
        transFlow.setStatus(1);
        transFlow.setMemo(paramsInfo);
        transFlowService.updateById(transFlow);

        //~~~~~~~~~~~~~~~~~更新投资者相关的表格~~~~~~~~~~~~~~~~~~~~~~~~
        //1.更新回款计划的状态
        //查询当前的lendReturn还款计划对应的所有的回款计划
        List<LendItemReturn> lendItemReturns = lendItemReturnService.list(Wrappers.lambdaQuery(LendItemReturn.class)
                .eq(LendItemReturn::getLendReturnId, lendReturn.getId()));

        lendItemReturns.forEach(lendItemReturn -> {
            //1.更新还款计划的状态等等
            lendItemReturn.setStatus(1);
            lendItemReturn.setOverdue(false);
            lendItemReturn.setOverdueTotal(new BigDecimal(0));
            BigDecimal fee = lendItemReturn.getInterest().
                    multiply(new BigDecimal("0.05"));
            lendItemReturn.setFee(fee);
            //2.更新投资人的交易流水状态为已完成
            //首先获取当前投资人的交易流水 根据lendItemReturn的id来获取
            TransFlow investTranFlow = transFlowService.getByTransNo(lendItemReturn.getId().toString());
            investTranFlow.setStatus(1);
            transFlowService.updateById(investTranFlow);

            //3.更新投资人的账户
            Long investUserId = lendItemReturn.getInvestUserId();
            userAccountService.updateAmountAndFreezeAmountByUserId(investTranFlow.getTransAmount(),new BigDecimal(0),
                    investUserId,new Date());
        });
        lendItemReturnService.updateBatchById(lendItemReturns);

        //4.判断是否是最后一期还款需要更新lend的状态为3已结清 lendItem 状态为2 已还款 更新借款信息为4 已完成
        if(lendReturn.getLast()){
            //进来这里面说明是最后的一期
            //更新lend状态为3 已结清
            Long lendId = lendReturn.getLendId();
            lendService.update(Wrappers.lambdaUpdate(Lend.class).eq(Lend::getId,lendId)
                    .set(Lend::getStatus, LendStatusEnum.PAY_OK.getStatus())
                    .set(Lend::getUpdateTime,new Date()));

            //更新lendItem状态为2 已还款 根据lendId来查询所有的投资记录
            lendItemService.update(Wrappers.lambdaUpdate(LendItem.class)
                    .eq(LendItem::getLendId,lendId)
                    .set(LendItem::getStatus,2)
                    .set(LendItem::getUpdateTime,new Date()));

            //更新借款信息状态为4 已完成
            borrowInfoService.update(Wrappers.lambdaUpdate(BorrowInfo.class)
                    .eq(BorrowInfo::getId,lendReturn.getBorrowInfoId())
                    .set(BorrowInfo::getStatus,4)
                    .set(BorrowInfo::getUpdateTime,new Date()));
        }
        return "success";
    }
}
